package seminar;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import seminar.evaluate.Evaluator;

/**
 * A semester and its timetable
 * @author Johannes Degler
 *
 */
public class Semester implements Comparable<Semester>{

	private final static Logger log = Logger.getLogger(Semester.class);

	private final String name;

	private final LectureEvent events[][] = new LectureEvent[5][6]; //[Day][Slot]

	private final Child child;

	private final List<Lecture> lectures;

	private boolean evaluated = false;

	private int points = 0;

	/**
	 * Create a new semester based on another semester but for another child<br>
	 * Room collisions will be tried to resolve
	 * @param parent The parent semester
	 * @param child The child this semester belongs to
	 * @throws DistributionException Thrown if distributing lectures fails (Shouldn't happen at this point)
	 */
	public Semester(final Semester parent, final Child child) throws DistributionException{
		Semester.log.debug("New semester: " + this);
		this.lectures = new ArrayList<Lecture>();

		//Copy values from parent1
		this.name = parent.name;
		this.child = child;

		//Create new events based on parent
		for(int day = 0; day < 5; day++)
			for(int slot = 0; slot < 6; slot++){
				final LectureEvent parentEvent = parent.getEvents()[day][slot];
				if(parentEvent == null)
					this.events[day][slot] = null;
				else{
					final Lecture lecture = new Lecture(parentEvent.getLecture().getName()); //NOPMD
					Room room = child.getRoomByName(parentEvent.getRoom().getDescription());

					//Is room already in use?
					if(room.getEvents()[day][slot] != null){
						Semester.log.debug("Found occupied room for semester \"" + this.name + "\" while merging semester time tables for child \"" + child + "\"");

						//Look for another room that is free at this time
						boolean otherRoomFound = false;
						for(final Room r : child.getRooms().values())
							if(r.getEvents()[day][slot] == null){
								room = r;
								Semester.log.debug("Found another free room :" + room);
								otherRoomFound = true;
								break;
							}

						//No other room found? Mark event for redistribution
						if(!otherRoomFound){
							Semester.log.debug("No other free room found, lecture \"" + lecture + "\" will be redistributed");
							this.lectures.add(lecture);
							continue;
						}
					}

					final LectureEvent event = new LectureEvent(lecture, room);
					Semester.log.debug("Adding \"" + event + "\" on day " + day + ", slot " + slot);
					this.events[day][slot] = event;
					room.addEvent(day, slot, event);
				}
			}

		//Distribute lectures that had no free room at their old day and slot
		this.distribute();
	}

	/**
	 * New semester (for initial parent generation)
	 * @param name The semester's name
	 * @param child The child this semester belongs to
	 * @param lectures Map of lectures (lecture + amount)
	 */
	public Semester(final String name, final Child child, final Map<Lecture, Integer> lectures){
		this.name = name;
		this.child = child;
		Semester.log.debug("New initial semester: " + this);
		this.lectures = this.lectureMapToList(lectures);
	}

	@Override
	public int compareTo(final Semester o) {
		return this.name.compareTo(o.name);
	}

	/**
	 * Distribute lectures
	 * @throws DistributionException Thrown if distributing lectures fails
	 */
	public final void distribute() throws DistributionException{
		Semester.log.debug("Distributing lectures for semester " + this);
		Collections.shuffle(this.lectures);
		for(final Lecture lecture : this.lectures){
			//Get a free room for the lecture
			final Room room = this.child.getFreeRoom(this.events);

			//Get a free point from the room
			final Point point = room.getFreePoint(this.events);

			//Save the event
			final LectureEvent lectureEvent = new LectureEvent(lecture, room);
			this.events[point.x][point.y] = lectureEvent;

			//Mark the room as used
			room.addEvent(point, lectureEvent);
		}

		this.lectures.clear();
	}

	/**
	 * Evaluate this semester
	 */
	public void evaluate(){
		Semester.log.debug("Evaluating semester " + this.name);
		new Evaluator(this).evaluate();
		this.evaluated = true;
	}

	/**
	 * Get lecture events
	 * @return Lecture events
	 */
	public LectureEvent[][] getEvents() {
		return this.events;
	}

	/**
	 * Get semester's name
	 * @return Semester's name
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Get semester's points
	 * @return Semester's points
	 */
	public int getPoints(){
		Semester.log.debug("Getting points for semester " + this);
		if(!this.evaluated)
			this.evaluate();

		return this.points;
	}

	/**
	 * Convert a map of lectures and their amount to a list
	 * @param map The map
	 * @return The list
	 */
	private List<Lecture> lectureMapToList(final Map<Lecture, Integer> map){
		final List<Lecture> lectures = new ArrayList<Lecture>(map.size());

		for(final Lecture lecture : map.keySet()){
			final int times = map.get(lecture);
			for(int i = 0; i < times; i++)
				lectures.add(lecture);
		}

		return lectures;
	}

	/**
	 * Mutate this semester
	 * @throws DistributionException Thrown if distributing lectures fails (Shouldn't happen at this point)
	 */
	public void mutate() throws DistributionException{
		Semester.log.debug("Mutating semester " + this);

		//How many lecture events should be redistributed?
		final int redist = (int)(Math.random() * 10);

		while(this.lectures.size() < redist){
			final int day = (int)(Math.random() * 5);
			final int slot = (int)(Math.random() * 6);
			final LectureEvent event = this.events[day][slot];
			this.events[day][slot] = null;
			if(event != null){
				this.lectures.add(event.getLecture());
				event.getRoom().getEvents()[day][slot] = null;
			}
		}

		this.distribute();
	}

	/**
	 * Set the semester's points
	 * @param points Semester's points
	 */
	public void setPoints(final int points) {
		this.points = points;
	}

	@Override
	public String toString() {
		return "Semester [" + this.name + "]";
	}
}
